package at.jku.pervasive.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Group;
import javax.media.j3d.LineStripArray;
import javax.media.j3d.Material;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.swing.JPanel;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import at.jku.pervasive.Application;
import at.jku.pervasive.Position;
import at.jku.pervasive.TagPosition;

import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewingPlatform;

public class View3DPanel extends JPanel
{

	double height = 2.8;
	double length = 15.0;
	double width = 10.0;

	double xOffset = 7.0;
	double yOffset = 0.0;
	double zOffset = 0.0;

	private final Point3d origin = new Point3d(0.0, 0.0, 0.0);
	private final Point3d offset = new Point3d(xOffset, yOffset, zOffset);
	private final Point3d centerOfRoom;

	// holds all the lines
	private BranchGroup lineArrayBranchGroup;

	// holds all tag positions
	private BranchGroup tagsBranchGroup;

	// holds the branch group for each tag position
	private final Map<TagPosition, BranchGroup> tagPositioBranchGroupMap = new TreeMap<TagPosition, BranchGroup>();

	// needed for resetting view
	private TransformGroup transformGroup;
	private ViewingPlatform viewingPlatform;

	@Deprecated
	private final Map<String, BranchGroup> tagMap = new TreeMap<String, BranchGroup>();

	@Deprecated
	private final Map<String, Color> colorMap = new TreeMap<String, Color>();

	public View3DPanel()
	{
		super();

		this.centerOfRoom = new Point3d(offset);
		this.centerOfRoom.add(new Point3d(width / 2.0, length / 2.0, height / 2.0));

		setLayout(new BorderLayout());

		GraphicsConfiguration configuration = SimpleUniverse.getPreferredConfiguration();
		Canvas3D canvas3d = new Canvas3D(configuration);

		add(BorderLayout.CENTER, canvas3d);

		SimpleUniverse universe = new SimpleUniverse(canvas3d);
		BranchGroup objectRoot = createBranchGroup();
		universe.addBranchGraph(objectRoot);

		OrbitBehavior orbit = new OrbitBehavior(canvas3d, OrbitBehavior.REVERSE_ALL | OrbitBehavior.STOP_ZOOM);
		BoundingSphere sphere = new BoundingSphere(origin, 1000.0);
		orbit.setSchedulingBounds(sphere);
		orbit.setZoomFactor(10.0);
		orbit.setTransFactors(5.0, 5.0);
		orbit.setMinRadius(0.5d);
		orbit.setBounds(sphere);
		orbit.setRotationCenter(centerOfRoom);

		viewingPlatform = universe.getViewingPlatform();
		viewingPlatform.setViewPlatformBehavior(orbit);

		resetView();

		setPreferredSize(new Dimension(650, 650));
	}

	public void clearAllPointsForTagIds()
	{
		this.tagsBranchGroup.removeAllChildren();
		this.tagMap.clear();
	}

	public void removePointForTagId(String tagId)
	{
		BranchGroup branchGroupToRemove = this.tagMap.remove(tagId);
		if (branchGroupToRemove != null)
		{
			tagsBranchGroup.removeChild(branchGroupToRemove);
		}
	}

	public void resetView()
	{
		TransformGroup viewTransformGroup = viewingPlatform.getMultiTransformGroup().getTransformGroup(0);

		Transform3D viewTransform3D = new Transform3D();
		Transform3D xRot = new Transform3D();
		xRot.rotX(Math.PI / 3.0);
		Transform3D zRot = new Transform3D();
		zRot.rotZ(Math.PI / 4.0);
		zRot.mul(xRot);
		viewTransform3D.mul(zRot);
		viewTransform3D.setTranslation(new Vector3d(xOffset + 19.0f, yOffset + -7.0f, zOffset + 14.0f));
		viewTransformGroup.setTransform(viewTransform3D);

		lineArrayBranchGroup.removeAllChildren();
	}

	public void setCurrentPositions(Position... positions)
	{
		// TODO create LineStripArray with given positions
		lineArrayBranchGroup.removeAllChildren();

		if (positions.length == 1)
		{
			updatePointForTag(positions[0], null);
			return;
		}

		BranchGroup branchGroup = new BranchGroup();
		branchGroup.setCapability(BranchGroup.ALLOW_DETACH);
		branchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
		lineArrayBranchGroup.addChild(branchGroup);

		LineStripArray positionsArray = new LineStripArray(positions.length, GeometryArray.COORDINATES,
				new int[] { positions.length });
		Point3d[] points = new Point3d[positions.length];
		for (int i = 0; i < positions.length; i++)
		{
			Position position = positions[i];
			points[i] = new Point3d(position.getX(), position.getY(), position.getZ());
		}

		positionsArray.setCoordinates(0, points);
		BranchGroup bg = new BranchGroup();
		bg.addChild(new Shape3D(positionsArray));
		branchGroup.addChild(bg);
	}

	public void showTagPositions(Map<String, List<Position>> tagPositions)
	{
		if (tagPositions.isEmpty())
			return;

		tagsBranchGroup.removeAllChildren();

		lineArrayBranchGroup.removeAllChildren();

		Set<String> keySet = tagPositions.keySet();
		for (String tagId : keySet)
		{
			List<Position> positions = tagPositions.get(tagId);

			if (positions.size() == 1)
			{
				BranchGroup tagBranchGroup = new BranchGroup();
				tagBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
				tagBranchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
				tagBranchGroup.setCapability(Group.ALLOW_CHILDREN_READ);
				tagBranchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);

				Appearance appearance = getAppearance(colorMap.get(tagId));
				Sphere sphere = new Sphere(0.1f, Primitive.GENERATE_NORMALS, 9, appearance);

				Transform3D tmpTrans = new Transform3D();
				Vector3d vector = getVectorForPosition(positions.get(0));
				tmpTrans.setTranslation(vector);
				TransformGroup tmpTransGroup = new TransformGroup();
				tmpTransGroup.setCapability(Group.ALLOW_CHILDREN_READ);
				tmpTransGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);
				tmpTransGroup.setTransform(tmpTrans);
				tmpTransGroup.addChild(sphere);
				tmpTransGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
				tagBranchGroup.addChild(tmpTransGroup);

				lineArrayBranchGroup.addChild(tagBranchGroup);
			}
			else
			{

				BranchGroup branchGroup = new BranchGroup();
				branchGroup.setCapability(BranchGroup.ALLOW_DETACH);
				branchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
				lineArrayBranchGroup.addChild(branchGroup);

				LineStripArray positionsArray = new LineStripArray(positions.size(), GeometryArray.COORDINATES,
						new int[] { positions.size() });
				Point3d[] points = new Point3d[positions.size()];

				for (int i = 0; i < positions.size(); i++)
				{
					Position position = positions.get(i);
					points[i] = new Point3d(position.getX(), position.getY(), position.getZ());
				}

				positionsArray.setCoordinates(0, points);
				Shape3D lineShape = new Shape3D(positionsArray);
				lineShape.setAppearance(getAppearance(colorMap.get(tagId)));

				BranchGroup bg = new BranchGroup();
				bg.addChild(lineShape);
				branchGroup.addChild(bg);
			}
		}
	}

	private Map<TagPosition, Position> currentPositions = new TreeMap<TagPosition, Position>();

	private TagPosition getTagPositionForTagId(String tagId)
	{
		return reverseTagAssignments.get(tagId);
	}

	public void updatePointForTag(Position newPosition, Color color)
	{
		// is there already a branch group in the map?
		String tagId = newPosition.getId();
		BranchGroup tagBranchGroup = tagMap.get(tagId);

		// update the color in the map
		colorMap.put(tagId, color);

		// update the current position for this tag
		TagPosition tagPosition = getTagPositionForTagId(tagId);
		if (tagPosition != null)
		{
			currentPositions.put(tagPosition, newPosition);

			// draw line from left to reference to right - if all 3 tag
			// positions are available
			if (currentPositions.size() == 3)
			{
				Position leftPos = currentPositions.get(TagPosition.LEFT);
				Position referencePos = currentPositions.get(TagPosition.REFERENCE);
				Position rightPos = currentPositions.get(TagPosition.RIGHT);

				LineStripArray stripArray = new LineStripArray(3, GeometryArray.COORDINATES, new int[] { 3 });

				List<Point3d> points = getPointArrayForPositions(leftPos, referencePos, rightPos);
				stripArray.setCoordinates(0, points.toArray(new Point3d[] {}));

				BranchGroup bg = new BranchGroup();
				bg.setCapability(BranchGroup.ALLOW_DETACH);
				bg.setCapability(Group.ALLOW_CHILDREN_EXTEND);

				bg.addChild(new Shape3D(stripArray));
				lineArrayBranchGroup.removeAllChildren();
				lineArrayBranchGroup.addChild(bg);
			}
		}

		if (tagBranchGroup == null)
		{
			// set new point
			tagBranchGroup = new BranchGroup();
			tagBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
			tagBranchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
			tagBranchGroup.setCapability(Group.ALLOW_CHILDREN_READ);
			tagBranchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);

			tagMap.put(tagId, tagBranchGroup);

			Appearance appearance = getAppearance(colorMap.get(tagId));
			Sphere sphere = new Sphere(0.1f, Primitive.GENERATE_NORMALS, 9, appearance);

			Transform3D tmpTrans = new Transform3D();
			Vector3d vector = getVectorForPosition(newPosition);
			tmpTrans.setTranslation(vector);
			TransformGroup tmpTransGroup = new TransformGroup();
			tmpTransGroup.setCapability(Group.ALLOW_CHILDREN_READ);
			tmpTransGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);
			tmpTransGroup.setTransform(tmpTrans);
			tmpTransGroup.addChild(sphere);
			tmpTransGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
			tagBranchGroup.addChild(tmpTransGroup);

			tagsBranchGroup.addChild(tagBranchGroup);
			return;
		}

		// get the transform group
		TransformGroup transGroup = (TransformGroup) tagBranchGroup.getChild(0);
		Transform3D transform = new Transform3D();
		transform.setTranslation(getVectorForPosition(newPosition));
		transGroup.setTransform(transform);

		// TODO update color too
	}

	private List<Point3d> getPointArrayForPositions(Position... positions)
	{
		List<Point3d> list = new ArrayList<Point3d>(positions.length);
		for (int i = 0; i < positions.length; i++)
		{
			Position pos = positions[i];
			Point3d tmp = new Point3d();
			tmp.x = pos.getX();
			tmp.y = pos.getY();
			tmp.z = pos.getZ();
			list.add(tmp);
		}
		return list;
	}

	protected BranchGroup createBranchGroup()
	{
		BranchGroup objectRoot = new BranchGroup();
		objectRoot.setCapability(BranchGroup.ALLOW_DETACH);
		objectRoot.setCapability(Group.ALLOW_CHILDREN_EXTEND);
		objectRoot.setCapability(Group.ALLOW_CHILDREN_WRITE);

		BranchGroup positionsBranchGroup = new BranchGroup();
		positionsBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
		positionsBranchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
		positionsBranchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);

		lineArrayBranchGroup = new BranchGroup();
		lineArrayBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
		lineArrayBranchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
		lineArrayBranchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);
		positionsBranchGroup.addChild(lineArrayBranchGroup);

		tagsBranchGroup = new BranchGroup();
		tagsBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
		tagsBranchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);
		tagsBranchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
		positionsBranchGroup.addChild(tagsBranchGroup);

		objectRoot.addChild(positionsBranchGroup);

		// add room
		LineStripArray room = getRoom();
		objectRoot.addChild(new Shape3D(room));

		// add lights
		double RADIUS = 100.0;
		objectRoot.addChild(getDirectinalLight(0.0, 0.0, 20.0, 0.0f, 0.0f, -1.0f, RADIUS)); // top
		objectRoot.addChild(getDirectinalLight(0.0, 0.0, -20.0, 0.0f, 0.0f, 1.0f, RADIUS)); // bottom
		objectRoot.addChild(getDirectinalLight(-20.0, 0.0, 0.0, 1.0f, 0.0f, 0.0f, RADIUS)); // left
		objectRoot.addChild(getDirectinalLight(20.0, 0.0, 0.0, -1.0f, 0.0f, 0.0f, RADIUS)); // right
		objectRoot.addChild(getDirectinalLight(0.0, -20.0, 0.0, 0.0f, 1.0f, 0.0f, RADIUS)); // front
		objectRoot.addChild(getDirectinalLight(0.0, 20.0, 0.0, 0.0f, -1.0f, 0.0f, RADIUS)); // back

		objectRoot.addChild(transformGroup);

		// compile
		objectRoot.compile();
		return objectRoot;
	}

	private Appearance getAppearance(Color color)
	{
		Appearance appearance = new Appearance();

		Color3f color3f = new Color3f(Color.YELLOW);
		if (color != null)
			color3f.set(color);

		Material material = new Material();
		material.setDiffuseColor(color3f);
		appearance.setMaterial(material);

		ColoringAttributes coloringAttributes = getColoringAttributes(color3f);
		appearance.setColoringAttributes(coloringAttributes);

		return appearance;
	}

	private ColoringAttributes getColoringAttributes(Color3f color3f)
	{
		return new ColoringAttributes(color3f, ColoringAttributes.FASTEST);
	}

	private DirectionalLight getDirectinalLight(double xOff, double yOff, double zOff, float xDir, float yDir,
			float zDir, double radius)
	{
		Color3f lightColor = new Color3f(Color.WHITE);
		Point3d point = new Point3d(centerOfRoom);
		point.add(new Point3d(xOff, yOff, zOff));
		BoundingSphere bounding = new BoundingSphere(point, radius);
		Vector3f lightdirection = new Vector3f(xDir, yDir, zDir);

		DirectionalLight directionalLight = new DirectionalLight(lightColor, lightdirection);
		directionalLight.setInfluencingBounds(bounding);
		return directionalLight;
	}

	private List<Point3d> getFloor(double height)
	{
		List<Point3d> list = new ArrayList<Point3d>(5);
		list.add(new Point3d(offset.x, offset.y, offset.z + height));
		list.add(new Point3d(offset.x + width, offset.y, offset.z + height));
		list.add(new Point3d(offset.x + width, offset.y + length, offset.z + height));
		list.add(new Point3d(offset.x, offset.y + length, offset.z + height));
		list.add(new Point3d(offset.x, offset.y, offset.z + height));
		return list;
	}

	private List<Point3d> getNormals()
	{
		List<Point3d> list = new ArrayList<Point3d>(8);
		list.add(offset);
		list.add(new Point3d(offset.x, offset.y, offset.z + height));

		list.add(new Point3d(offset.x + width, offset.y, offset.z));
		list.add(new Point3d(offset.x + width, offset.y, offset.z + height));

		list.add(new Point3d(offset.x + width, offset.y + length, offset.z));
		list.add(new Point3d(offset.x + width, offset.y + length, offset.z + height));

		list.add(new Point3d(offset.x, offset.y + length, offset.z));
		list.add(new Point3d(offset.x, offset.y + length, offset.z + height));
		return list;
	}

	private LineStripArray getRoom()
	{
		Point3d[] floor = new Point3d[] { new Point3d(0.0, 0.0, 0.0), new Point3d(width, 0.0, 0.0),
				new Point3d(width, length, 0.0), new Point3d(0.0, length, 0.0), new Point3d(0.0, 0.0, 0.0),
				new Point3d(0.0, 0.0, height), new Point3d(width, 0.0, height), new Point3d(width, length, height),
				new Point3d(0.0, length, height), new Point3d(0.0, 0.0, height) };
		int vertexCount = floor.length;

		LineStripArray room = new LineStripArray(vertexCount, GeometryArray.COORDINATES, new int[] { floor.length });
		room.setCoordinates(0, floor);

		room = new LineStripArray(18, GeometryArray.COORDINATES, new int[] { 5, 5, 2, 2, 2, 2 });
		List<Point3d> points = new LinkedList<Point3d>();
		points.addAll(getFloor(0.0));
		points.addAll(getFloor(height));
		points.addAll(getNormals());
		room.setCoordinates(0, points.toArray(new Point3d[points.size()]));

		return room;
	}

	private Vector3d getVectorForPosition(Position newPosition)
	{
		return new Vector3d(newPosition.getX(), newPosition.getY(), newPosition.getZ());
	}

	private Map<String, TagPosition> reverseTagAssignments = new TreeMap<String, TagPosition>();
	private Map<TagPosition, String> tagAssignments = new TreeMap<TagPosition, String>();

	public void tagAssignmentChanged(TagPosition tagPosition, String tagId)
	{
		reverseTagAssignments.put(tagId, tagPosition);
		tagAssignments.put(tagPosition, tagId);
		repaint();
	}

	public void updateTagPosition(TagPosition tagPosition, Position newPosition)
	{
		if (TagPosition.CHEST == tagPosition || TagPosition.BACK == tagPosition)
			return;
		
		// is there already a branch group in the map?
		BranchGroup tagPositionBranchGroup = tagPositioBranchGroupMap.get(tagPosition);

		// update the current position for this tag
		currentPositions.put(tagPosition, newPosition);

		// draw line from left to reference to right - if all 3 tag
		// positions are available
		Position leftPos = currentPositions.get(TagPosition.LEFT);
		Position referencePos = currentPositions.get(TagPosition.REFERENCE);
		Position rightPos = currentPositions.get(TagPosition.RIGHT);
		if (leftPos != null && referencePos != null && rightPos != null)
		{
			LineStripArray stripArray = new LineStripArray(3, GeometryArray.COORDINATES, new int[] { 3 });

			List<Point3d> points = getPointArrayForPositions(leftPos, referencePos, rightPos);
			stripArray.setCoordinates(0, points.toArray(new Point3d[] {}));

			BranchGroup bg = new BranchGroup();
			bg.setCapability(BranchGroup.ALLOW_DETACH);
			bg.setCapability(Group.ALLOW_CHILDREN_EXTEND);

			bg.addChild(new Shape3D(stripArray));
			lineArrayBranchGroup.removeAllChildren();
			lineArrayBranchGroup.addChild(bg);
		}

		if (tagPositionBranchGroup == null)
		{
			// set new point
			tagPositionBranchGroup = new BranchGroup();
			tagPositionBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
			tagPositionBranchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
			tagPositionBranchGroup.setCapability(Group.ALLOW_CHILDREN_READ);
			tagPositionBranchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);

			// set branch group for this tag position
			tagPositioBranchGroupMap.put(tagPosition, tagPositionBranchGroup);

			// getting the color
			Color color = Application.getInstance().getColorForTagPosition(tagPosition);
			Appearance appearance = getAppearance(color);
			Sphere sphere = new Sphere(0.1f, Primitive.GENERATE_NORMALS, 9, appearance);

			Transform3D tmpTrans = new Transform3D();
			Vector3d vector = getVectorForPosition(newPosition);
			tmpTrans.setTranslation(vector);
			TransformGroup tmpTransGroup = new TransformGroup();
			tmpTransGroup.setCapability(Group.ALLOW_CHILDREN_READ);
			tmpTransGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);
			tmpTransGroup.setTransform(tmpTrans);
			tmpTransGroup.addChild(sphere);
			tmpTransGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
			tagPositionBranchGroup.addChild(tmpTransGroup);

			tagsBranchGroup.addChild(tagPositionBranchGroup);
			return;
		}

		// get the transform group
		TransformGroup transGroup = (TransformGroup) tagPositionBranchGroup.getChild(0);
		Transform3D transform = new Transform3D();
		transform.setTranslation(getVectorForPosition(newPosition));
		transGroup.setTransform(transform);
	}
}
